/*-------------------------------------------------------------------------
 *
 * fileset.c
 *	  管理命名的临时文件。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/file/fileset.c
 *
 * FileSets 提供一个临时命名空间（类似目录），以便通过名称发现文件。
 *
 * 当临时文件需要多次打开/关闭，并且基础文件需要在事务之间生存时，后端可以使用 FileSets。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <limits.h>

#include "catalog/pg_tablespace.h"
#include "commands/tablespace.h"
#include "common/hashfn.h"
#include "miscadmin.h"
#include "storage/ipc.h"
#include "storage/fileset.h"
#include "utils/builtins.h"

static void fc_FileSetPath(char *fc_path, FileSet *fc_fileset, Oid fc_tablespace);
static void fc_FilePath(char *fc_path, FileSet *fc_fileset, const char *fc_name);
static Oid	fc_ChooseTablespace(const FileSet *fc_fileset, const char *fc_name);


/*
 * 初始化临时文件的空间。此 API 可被共享
 * 文件集使用，也可以在临时文件仅由单个后端使用的情况下使用，
 * 但文件需要多次打开和关闭，并且
 * 底层文件需要在事务之间保存。
 *
 * 调用者需显式通过使用
 * FileSetDelete/FileSetDeleteAll 来移除此类文件。
 *
 * 文件将分布在配置在
 * temp_tablespaces 中的表空间上。
 *
 * 在底层，这个集合是一个或多个最终
 * 将被删除的目录。
 */
void FileSetInit(FileSet *fc_fileset)
{
	static uint32 fc_counter = 0;

	fc_fileset->creator_pid = MyProcPid;
	fc_fileset->number = fc_counter;
	fc_counter = (fc_counter + 1) % INT_MAX;

	/* 捕获表空间 OID，以便所有后端对此达成一致。 */
	PrepareTempTablespaces();
	fc_fileset->ntablespaces =
		GetTempTablespaces(&fc_fileset->tablespaces[0],
						   lengthof(fc_fileset->tablespaces));
	if (fc_fileset->ntablespaces == 0)
	{
		/* 如果 GUC 为空，使用当前数据库的默认表空间 */
		fc_fileset->tablespaces[0] = MyDatabaseTableSpace;
		fc_fileset->ntablespaces = 1;
	}
	else
	{
		int			fc_i;

		/*
		 * InvalidOid 的一个条目意味着使用当前数据库的默认表空间。
		 * 现在替换它，以确保所有 FileSet 的用户达成一致。
		 */
		for (fc_i = 0; fc_i < fc_fileset->ntablespaces; fc_i++)
		{
			if (fc_fileset->tablespaces[fc_i] == InvalidOid)
				fc_fileset->tablespaces[fc_i] = MyDatabaseTableSpace;
		}
	}
}

/*
 * 在给定集合中创建一个新文件。
 */
File FileSetCreate(FileSet *fc_fileset, const char *fc_name)
{
	char		fc_path[MAXPGPATH];
	File		fc_file;

	fc_FilePath(fc_path, fc_fileset, fc_name);
	fc_file = PathNameCreateTemporaryFile(fc_path, false);

	/* 如果失败，检查是否需要按需创建目录。 */
	if (fc_file <= 0)
	{
		char		fc_tempdirpath[MAXPGPATH];
		char		fc_filesetpath[MAXPGPATH];
		Oid			fc_tablespace = fc_ChooseTablespace(fc_fileset, fc_name);

		TempTablespacePath(fc_tempdirpath, fc_tablespace);
		fc_FileSetPath(fc_filesetpath, fc_fileset, fc_tablespace);
		PathNameCreateTemporaryDir(fc_tempdirpath, fc_filesetpath);
		fc_file = PathNameCreateTemporaryFile(fc_path, true);
	}

	return fc_file;
}

/* 打开一个使用 FileSetCreate() 创建的文件 */
File FileSetOpen(FileSet *fc_fileset, const char *fc_name, int fc_mode)
{
	char		fc_path[MAXPGPATH];
	File		fc_file;

	fc_FilePath(fc_path, fc_fileset, fc_name);
	fc_file = PathNameOpenTemporaryFile(fc_path, fc_mode);

	return fc_file;
}

/* 删除一个使用 FileSetCreate() 创建的文件。 */ 
 /* 如果文件存在则返回 true，如果不存在则返回 false。 */
bool FileSetDelete(FileSet *fc_fileset, const char *fc_name,
			  bool fc_error_on_failure)
{
	char		fc_path[MAXPGPATH];

	fc_FilePath(fc_path, fc_fileset, fc_name);

	return PathNameDeleteTemporaryFile(fc_path, fc_error_on_failure);
}

/* 删除集合中的所有文件。 */
void FileSetDeleteAll(FileSet *fc_fileset)
{
	char		fc_dirpath[MAXPGPATH];
	int			fc_i;

	/* 删除我们在每个表空间中创建的目录。不会失败，因为我们在错误清理路径中使用这个，但可能会在 IO 错误时生成日志消息。 */
	for (fc_i = 0; fc_i < fc_fileset->ntablespaces; ++fc_i)
	{
		fc_FileSetPath(fc_dirpath, fc_fileset, fc_fileset->tablespaces[fc_i]);
		PathNameDeleteTemporaryDir(fc_dirpath);
	}
}

/* 为在给定表空间中持有支持 FileSet 的文件的目录构建路径。 */
static void fc_FileSetPath(char *fc_path, FileSet *fc_fileset, Oid fc_tablespace)
{
	char		fc_tempdirpath[MAXPGPATH];

	TempTablespacePath(fc_tempdirpath, fc_tablespace);
	snprintf(fc_path, MAXPGPATH, "%s/%s%lu.%u.fileset",
			 fc_tempdirpath, PG_TEMP_FILE_PREFIX,
			 (unsigned long) fc_fileset->creator_pid, fc_fileset->number);
}

/* 排序必须确定给定的临时文件属于哪个表空间。 */
static Oid fc_ChooseTablespace(const FileSet *fc_fileset, const char *fc_name)
{
	uint32		fc_hash = hash_any((const unsigned char *) fc_name, strlen(fc_name));

	return fc_fileset->tablespaces[fc_hash % fc_fileset->ntablespaces];
}

/* 计算 FileSet 中文件的完整路径。 */
static void fc_FilePath(char *fc_path, FileSet *fc_fileset, const char *fc_name)
{
	char		fc_dirpath[MAXPGPATH];

	fc_FileSetPath(fc_dirpath, fc_fileset, fc_ChooseTablespace(fc_fileset, fc_name));
	snprintf(fc_path, MAXPGPATH, "%s/%s", fc_dirpath, fc_name);
}
